# -*- coding: utf-8 -*-
"""
Created on Tue Jun  4 09:31:11 2019

@author: solydo
"""
import numpy as np
import TrainTree as tt

def load_yeast(path):
    '''
    载入蛋白质定位位点数据
    根据一些指标判断蛋白质所在部位
    特征数：1个序列名(无预测性)，8个连续型
    分类数:10
    '''
    # 直接构造特征名
    feature_names=['mcg','gvh','alm','mit','erl','pox','vac','nuc']
    #  构造类别名
    label_names={0:'CYT',1:'NUC',2:'MIT',3:'ME3',4:'ME2',5:'ME1',6:'EXC',7:'VAC',8:'POX',9:'ERL'}    
    #  导入样本数据
    x,y=[],[]
    f=open(path,'r')
    for line in f:
        ls=line.strip('\n').split('  ')
        if len(ls)<len(feature_names)+1:  #最后一行为空，剔除该情况
            continue
        for i in range(len(ls))[::-1]:
            if len(ls[i])==0:
                ls.pop(i)            
        x.append([float(i) for i in ls[1:-1]])
        y.append(list(label_names.keys())[list(label_names.values()).index(ls[-1])])
    f.close()
    return {'data':x,'target':y,
            'feature_names':feature_names,'label_names':label_names}

def load_ionosphere(path):
    '''
    载入电离层数据
    根据根据一些雷达信号的特征判断电离层中是否存在某种结构
    特征数34个连续型
    分类数2(good,bad)
    '''
    #  构造特征名
    feature_names=[]
    for i in range(17):
        feature_names.append('Real-'+str(i+1))
        feature_names.append('Image-'+str(i+1))
    #  构造类别名
    label_names={0:'Good',1:'Bad'}
    #  导入样本数据
    x,y=[],[]
    f=open(path,'r')
    for line in f:
        ls=line.strip('\n').split(',')
        if len(ls)<len(feature_names)+1:  #最后一行为空，剔除该情况
            continue
        x.append([float(i) for i in ls[:-1]])
        y.append((ls[-1]=='b')+0)         #最后一个数据代表类别,若为'b',转换为1，否则转换为0
    f.close()
    return {'data':x,'target':y,
            'feature_names':feature_names,'label_names':label_names}    

def DataSplit(X,Y,ratio):
    # 划分训练集和测试集，其中训练集所占比例为ratio
    y=list(set(Y))  # y的可能取值，亦即类别数
    m=[]            # 存储各个类别的样本数
    index=[]        # 存储各个类别的索引号
    for yy in y:    # 计算m和index
        m.append(sum(np.array(Y)==yy))
        index.append(list(np.arange(len(Y))[np.array(Y)==yy]))
    for i in index:
        np.random.shuffle(i)  # 将index中每一类别内部的索引号打乱
    mt=[]           # 测试集中所包含的各个类别的样本数
    for mm in m:
        mt.append(int(mm*ratio//1)+(np.random.rand()>0.5))
    X=list(X); Y=list(Y)
    Xt,Xv,Yt,Yv=[],[],[],[]  # 训练集和测试集
    for i in range(len(index)):
        Xt+=[X[k] for k in index[i][:mt[i]]]
        Yt+=[Y[k] for k in index[i][:mt[i]]]
        Xv+=[X[k] for k in index[i][mt[i]:]]
        Yv+=[Y[k] for k in index[i][mt[i]:]]
    return Xt,Yt,Xv,Yv

def hold_out(X,Y,FeatureName,LabelName,N,ratio,**kw):
    '''
    N次留出法求平均,ratio为训练集所占比例
    **kw关键字设置相关参数，关键字的值均为列表，表示多种取值可能，
        需要遍历所有可能的组合情况
    输出结果为字典型, erro['00']=erro_value
        '00'代表两个组合参数分别取第0和第0个值
    '''
    #==================================
    #  1.从关键字中解析出多种组合方式
    #----------------------------------
    #  比方说有4个关键字，分别取值数目为3,3,1,4,
    #  那么，编程实现得4重循环，
    #  但是对于不同的情况，还可能时3重，6重循环，等等，难以统一编程实现。
    #  这里采用编码方法，比如有四个参数，分别有取值数目3,3,1,4，那么总共有3*3*1*4=36种组合方式
    #  转换为36个编码[0,0,0,0]~[2,2,0,3]
    #==================================
    num=[len(kw[key])for key in kw.keys()]  #各个关键字的取值可能数
    G=1         #组合总数
    weight=[]   #各编码位的权重
    for n in num[::-1]:
        weight.append(G)
        G*=n
    weight=weight[::-1]
    code=[]
    codestr=[]
    for n in range(G):
        number=n
        coden=[]
        codestrn=''
        for w in weight:
            coden.append(number//w)
            codestrn+=str(number//w)
            number=number%w
        code.append(coden)
        codestr.append(codestrn)
    #  举例说明，得到的code和codestr分别为[1,2,0,1]和'1201'
    
    #====================================
    #       2.对每种参数组合情况下估计其错误率
    #====================================
    erro={}
    for g in range(G):
        print('执行第%d个算法'%(g+1))
        codeg=code[g]         #编码
        codestrg=codestr[g]  #编码字符串
        kwg={}                #某一个关键字组合方式
        i=0
        for key in kw.keys():
            kwg[key]=kw[key][codeg[i]]
            i+=1
        errog=[]              #存储错误率
        for n in range(N):
            print(n+1,end=',')
            Xt,Yt,Xv,Yv=DataSplit(X,Y,ratio)    #训练集和验证集
            Xt,Yt,Xp,Yp=DataSplit(Xt,Yt,ratio)  #进一步把训练集划分为训练集和剪枝集
            tree=tt.CreatTree(Xt,Yt,Xp,Yp,FeatureName,LabelName,**kwg)
            Ypre=tt.predict(tree,Xv,FeatureName,LabelName)
            errog.append(sum(np.array(Ypre)!=np.array(Yv))/len(Yv))
        erro[codestrg]=sum(errog)/N
        print('',end='\n')
    return erro

def erro_to_order(erro):
    # 将错误率转换为序值
    # 基于'TranTree.py'中函数 w_to_order修改而来
    w=np.array(list(erro.values()))         #权重值向量
    w_values=list(set(w)); w_values.sort()  #w_values是权重所有可能取值
    order_values=np.zeros(len(w))      #序值向量
    ov=1                        #当前具体序值
    for wv in w_values:
        N=sum(w==wv)  #当前权重值相等取值数目
        order_values[w==wv]=ov+(N-1)/2
        ov+=N
    order={}
    i=0
    for key in erro.keys():
        order[key]=order_values[i]
        i+=1
    return order